/*
 *	CHANGE LOG
 * 
 * 10/02/2008 - DW - Made change to remove post reply instructions.  
 * 
 * * 01.00.03
 *
 *
 *		10/12/2008 - DW - Changed the aliases in GetModulesForUser. They were after the (nolock) hint,
 *						but this seemed to cause errors on some installs of SQL Server.
 * 
 * * 01.00.04
 *
 *
 *		12/27/2008 - DW - Added code to authorize calls to methods
 * 
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Users;
using DotNetNuke.Modules.Forum;
using DotNetNuke.Security;
using Config=DotNetNuke.Modules.Forum.Config;
using DataProvider = DotNetNuke.Data.DataProvider;

namespace ITCrossing
{
	public class metaPostForumsProvider : IPublishable
	{
		# region IPublishable Members

		# region Public Properties

		/// <remarks>
		/// ProviderKey should be set to the Friendly Name of the module for
		/// which this provider is being implemented.
		/// </remarks>
		public string ProviderKey
		{
			get
			{
				return MetaPostServices.GetFriendlyNameFromModuleDefinition("Forum");
			}
		}

		/// <remarks>
		/// ManifestFilePath is the path including the filename to the manifest file used to determine 
		/// the settings available in Windows Live Writer.  An example path might
		/// be /DesktopModules/itcMetaPost/manifests/wlwblog.xml.  See the following URL for 
		/// information regarding what Content can be placed in a WLW manifest file:
		/// http://msdn2.microsoft.com/en-us/library/bb463260.aspx
		/// </remarks>
		public string ManifestFilePath
		{
			get { return "/DesktopModules/itcMetaPost/manifests/wlwforum.xml"; }
		}

		/// <remarks>
		/// LocalizationFilePath is the path including the filename to the 
		/// resource file used by the module to retrieve the error messages
		/// shown to the user (eg. "/DesktopModules/custommodule/App_LocalResources/customfilename")
		/// Note that the filename should not include the file extension.
		/// </remarks>
		public string LocalizationFilePath
		{
			get { return "/DesktopModules/itcMetaPost/App_LocalResources/itcMetaPostForum"; }
		}

		/// <remarks>
		/// OPTIONAL - ImageUploadPath is a property metaPost uses to determine where 
		/// to store the media files (images).  This property should return just a 
		/// section of the path starting from the portal root.  Added to this path will
		/// be the username along with the path 
		/// </remarks>
		public string ImageUploadPath
		{
			get
			{
				// This can be overridden with custom implementation if needed.  If
				// overridden, then a message should be placed in the ImagePath column
				// for your provider letting administrators know that administration of
				// the ImagePath is implemented elsewhere.  
				return MetaPostServices.GetProviderImageUploadPath(ProviderKey);
			}
		}

		/// <remarks>
		/// AttachmentUploadPath is used to specify the location of attachments added
		/// to a post or page.
		/// </remarks>
		public string AttachmentUploadPath
		{
			get
			{
				// For now, we'll just set this to the same as the image upload path
				return MetaPostServices.GetProviderAttachmentUploadPath(ProviderKey);
			}
		}

		/// <remarks>
		/// SettingsUserControlPath is for future use.  It will return the path to the
		/// the settings user control for this provider.  
		/// </remarks>
		public string SettingsUserControlPath
		{
			get { throw new NotImplementedException(); }
		}

		# endregion

		# region Get Methods
		/// <remarks>
		/// The purpose of this procedure is to provide a list of all the modules in a 
		/// given portal to which the currently logged on user has either insert, update
		/// or delete access.  Note, this may not always correspond to an actual module.
		/// For example, with the DNN Blog module provider, a list of blogs is returned
		/// along with URL of where each blog is available within the site.  For the News
		/// Articles module, we return a list of all News Articles modules that have been
		/// added to Tabs throughout the site.
		/// </remarks>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns></returns>
		/// <param name="providerKey"></param>
		public ModuleInfoStruct[] GetModulesForUser(UserInfo userInfo, PortalSettings portalSettings, string providerKey)
		{
			//Retrieve the ForumInfo object for the currently logged in user.
			ForumController forumController = new ForumController(); 
			GroupController groupController = new GroupController();
			GroupInfo group;
			UserForumsController userForumController = new UserForumsController();
			List<ModuleInfoStruct> moduleInfos = new List<ModuleInfoStruct>();
			List<int> userForumIdList = new List<int>();
			
			//ForumInfo objForumInfo = ForumController. .GetForumByUserName(userInfo.PortalID, userInfo.Username);

			string SQL;
			if (userInfo.IsSuperUser)
			{
				SQL = "SELECT DISTINCT f.ForumId \n"
					  + "FROM         {databaseOwner}{objectQualifier}Forum_Forums f (nolock) \n"
					  +	"	JOIN {databaseOwner}{objectQualifier}Forum_Groups g (nolock) ON f.GroupId = g.GroupId \n"
					  + "WHERE f.IsActive = 1 \n"
					  + "	AND f.ForumType <> 2 \n"
					  + "	AND g.PortalID = " + portalSettings.PortalId.ToString();
			}
			else
			{
				SQL = "DECLARE @UserId int \n"
					+ "SELECT @UserId = " + userInfo.UserID.ToString() + " \n"
					+ "SELECT DISTINCT fp.forumId \n"
					+ "FROM         {databaseOwner}{objectQualifier}UserRoles ur (nolock) \n"
					+ "     JOIN {databaseOwner}{objectQualifier}Forum_ForumPermission fp (nolock) ON fp.RoleId = ur.RoleID \n"
					+ "     JOIN {databaseOwner}{objectQualifier}Forum_Forums f (nolock) ON fp.ForumId = f.ForumId \n"
					+ "     JOIN {databaseOwner}{objectQualifier}Forum_Groups g (nolock) ON g.GroupID = f.GroupID \n"
					+ "WHERE ur.UserId = @UserId \n"
					+ "	AND fp.PermissionID IN (1,2,3,4) \n"
					+ "	AND f.IsActive = 1 \n"
					+ "	AND f.ForumType <> 2 \n"
					+ "	AND g.PortalID = " + portalSettings.PortalId.ToString() + " \n"
					+ " \n"
					+ "UNION  \n"
					+ "SELECT fp.forumId FROM {databaseOwner}{objectQualifier}Forum_ForumPermission fp (nolock) WHERE fp.PermissionId = 1 AND fp.UserID = @UserId "
					+ "UNION \n"
					+ "SELECT f.ForumId FROM {databaseOwner}{objectQualifier}Forum_Forums f (nolock) \n"
					+ "		JOIN {databaseOwner}{objectQualifier}Forum_Groups g (nolock) ON g.GroupID = f.GroupID \n"
					+ "WHERE f.ForumType <> 2 \n"
					+ "		AND f.PublicView = 1 \n"
					+ "		AND f.PublicPosting = 1 \n"
					+ "		AND f.IsActive = 1 \n"
					+ "		AND g.PortalID = " + portalSettings.PortalId.ToString() + " \n";
			}
			
			using (IDataReader dr = DataProvider.Instance().ExecuteSQL(SQL))
			{
				while (dr.Read())
				{
					userForumIdList.Add(dr.GetInt32(0)); // The first and only column returned is the ForumId
				}
			}

			foreach (int forumId in userForumIdList)
			{
				ForumInfo forum = forumController.GetForum(forumId);
				group = groupController.GroupGet(forum.GroupID);
				
				// Get name of the Forum
				Hashtable settings = PortalSettings.GetModuleSettings(forum.ModuleID);
				string name = settings["Name"] as string;
				if (name == null) name = string.Empty;

				ModuleInfoStruct structForumInfo = new ModuleInfoStruct();
				structForumInfo.ModuleLevelId = forumId.ToString();
				structForumInfo.ModuleName = name + " :: " + HttpUtility.HtmlDecode(group.Name) + " :: " +  HttpUtility.HtmlDecode(forum.Name);
				int tabId = GetTabId(forumId.ToString());
				if (tabId != -1)
				{
					structForumInfo.Url = MetaPostServices.GetRedirectUrl(providerKey, tabId);
					moduleInfos.Add(structForumInfo);	
				}
			}

			if (moduleInfos.Count == 0)
			{
				throw new MetaPostException("NoModulesForUser",
											"There is no Forum associated with this user account.  Please enter a user account which has been associated with a Forum.");
			}
			moduleInfos.Sort(new ModuleInfoStructComparer());
			return moduleInfos.ToArray();
		}

		/// <remarks>
		/// GetRecentItems returns a list of the recent items added to the module.  For example, with
		/// the blog module, the list would return all the recent blog entries.  For the Text/HTML provider, 
		/// this method would return a list of the recently added Text/HTML modules in the site.
		/// </remarks>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="numberOfItems"></param>
		/// <param name="requestType">GetRecentItems can be called for a number of reasons.  In Windows Live Writer,
		/// users are able to retrieve a list of recent posts or recent pages.  The requestType parameter is used
		/// to denote whether a list of pages or posts is being requested.  Additionally, WLW may request a list 
		/// of hiearchical pages to show pages when allowing a parent page to be assigned to a new page.  The values for this enum
		/// are RecentPosts, RecentPages and RecentPagesHiearchical.</param>
		/// <param name="providerKey"></param>
		/// <returns></returns>		
		public Item[] GetRecentItems(string moduleLevelId, UserInfo userInfo, PortalSettings portalSettings, int numberOfItems, RecentItemsRequestType requestType, string providerKey)
		{

			// Authorize user for call to this method
			MetaPostServices.AuthorizeUser(AuthorizationType.ModuleLevelId, moduleLevelId,
										   GetModulesForUser(userInfo, portalSettings, ProviderKey));			

			List<Item> itemArray = new List<Item>();
			ForumController forumController = new ForumController();
			ThreadController threadController = new ThreadController();
			ModuleController moduleController = new ModuleController();
			int tabId;
			int totalRecords = -1;
			int forumId = Convert.ToInt32(moduleLevelId);
			ForumInfo forum = forumController.GetForum(forumId);

			// We're trying to get the TabId so we can get the link below.
			ModuleInfo moduleInfo = moduleController.GetModule(forum.ModuleID, Null.NullInteger);

			tabId = GetTabId(forumId.ToString());

			//ArrayList threads = threadController.GetByForum(userInfo.UserID, forumId);
			List<ThreadInfo> threads =
				threadController.ThreadListGetCached(moduleInfo.ModuleID, forumId, numberOfItems, 0,
				                                     string.Empty, portalSettings.PortalId,ref totalRecords);

			foreach (ThreadInfo thread in threads)
			{
				if (requestType == RecentItemsRequestType.RecentPosts)
				{
					Item item = new Item();
					PostInfo lastPost = thread.LastApprovedPost;
					if (lastPost != null)
					{
						item.Link = Utils.ContainerViewThreadLink(tabId, forumId, thread.ThreadID);
						item.Content = HttpUtility.HtmlDecode(thread.Body);
						item.DateCreated =
							Utils.ConvertTimeZone(thread.CreatedDate, Config.GetForumConfig(thread.ModuleID));
						item.StartDate = thread.CreatedDate;
						item.ItemId = lastPost.PostID;
						item.Title = lastPost.Subject + " (" + thread.TotalPosts + ")";
						item.Permalink = Utils.ContainerViewPostLink(tabId, forumId, thread.ThreadID, lastPost.PostID);

						itemArray.Add(item);
					}
				}
				else
				{
					// We want to see all the pages.
					PostController postController = new PostController();
					ArrayList posts =
						postController.PostGetAllForThread(thread.ThreadID, portalSettings.PortalId);
					foreach (PostInfo post in posts)
					{
						Item item = new Item();
						//item.Link = Utils.ContainerViewThreadLink(tabId, forumId, thread.ThreadID);
						item.Content = HttpUtility.HtmlDecode(post.Body);
						item.DateCreated =
							Utils.ConvertTimeZone(post.CreatedDate,
												  Config.GetForumConfig(thread.ModuleID));
						item.StartDate = post.CreatedDate;
						item.ItemId = post.PostID;
						item.Title = thread.Subject + " :: " + post.Subject;
						item.Permalink = Utils.ContainerViewPostLink(tabId, forumId, thread.ThreadID, post.PostID);

						itemArray.Add(item);
					}
				}
			}

			itemArray.Sort(new ItemComparer());

			return itemArray.ToArray();
		}

		/// <remarks>
		/// GetItem is used to retrieve a specific item, which may be a page or a post if your module supports
		/// both concepts.  In cases where a module supports both pages and posts, a concept made available in 
		/// Windows Live Writer, but not in Word 2007 or in many other publishing clients, the itemType parameter 
		/// can be used to distinguish between when this method is being called to add a page or a post.  If your 
		/// provider doesn't support a distinction between pages and posts, then simply ignore the itemType parameter.
		/// </remarks>
		/// <param name="itemId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="itemType"></param>
		/// <returns></returns>
		public Item GetItem(string itemId, UserInfo userInfo, PortalSettings portalSettings, ItemType itemType)
		{

			
			PostController postController = new PostController();
			Item item = new Item();
			int tabId;

			PostInfo postInfo;

			postInfo = postController.PostGet(Convert.ToInt32(itemId), portalSettings.PortalId);

			// Authorize user for call to this method
			MetaPostServices.AuthorizeUser(AuthorizationType.ModuleLevelId, postInfo.ForumID.ToString(),
										   GetModulesForUser(userInfo, portalSettings, ProviderKey));			

			tabId = GetTabId(postInfo.ForumID.ToString());

			if (postInfo == null)
				throw new MetaPostException("NoPostAvailable",
											"There was en error retrieving the Forum entry.  Try closing and restarting the software you're using to edit your Forum post.");
			item.Link = Utils.ContainerViewPostLink(tabId, postInfo.ForumID, postInfo.ThreadID, postInfo.PostID);

			ForumUser author = postInfo.Author;

			string previousPostsMessage;
			string previousPostHeader;
			string previousPostFooter;
			if (userInfo.UserID == author.UserID)
			{
				previousPostsMessage = MetaPostServices.GetString("EditOwnPostMessage",
				                                                  "Remove this line and everything above it to edit your existing post.",
				                                                  LocalizationFilePath, portalSettings.DefaultLanguage);
				previousPostHeader = string.Empty;
				previousPostFooter = string.Empty;
			}
			else
			{
				previousPostsMessage = MetaPostServices.GetString("PreviousPostsMessage",
				                                                  "Previous posts shown for reference.  These previous posts are excluded from your reply.",
				                                                  LocalizationFilePath, portalSettings.DefaultLanguage);
				previousPostHeader = "<br /><br /><hr style=\"text-align:left;width:300px;\" /><strong>" + postInfo.Subject + "</strong><br />" +
									  GetAuthorInfo(postInfo, portalSettings) + "<hr style=\"text-align:left;width:300px;color:#cccccc;height:1px;\" /><br />";
				previousPostFooter = "";
			}

			string editContentMessage = "<p style=\"font-weight:bold\">"
			              + previousPostsMessage
			              + "</p>";

			item.Content = "<br />"
				+ MetaPostServices.GetString("PostReplyInstructions", "Enter reply above this line.",
				                           LocalizationFilePath, portalSettings.DefaultLanguage)
				+ "<!--more--><!--itcMore-->" + editContentMessage
				+ previousPostHeader
				+ HttpUtility.HtmlDecode(postInfo.Body)
				+ previousPostFooter;

			# region Show Previous 5 Posts

			// Only show previous posts if this post was added by someone else.  If we are opening one of our
			// own posts (if the user is opening one of their own posts), then we will not show the previous entries
			// since this may be an attempt to edit the post.

			if (postInfo.Author.UserID != userInfo.UserID)
			{
				// Loop through the previous posts to show the post history at least 5 layers deep
				ArrayList previousPosts =
					postController.PostGetAllForThread(postInfo.ThreadID, portalSettings.PortalId);
				List<PostInfo> previousPostsToShow = new List<PostInfo>();
				foreach (PostInfo previousPost in previousPosts)
				{
					if (previousPost.PostID < postInfo.PostID)
					{
						previousPostsToShow.Add(previousPost);
					}
				}

				previousPostsToShow.Sort(new PostInfoComparer());

				int i = 0;
				foreach (PostInfo previousPost in previousPostsToShow)
				{
					i++;
					// We only want to show the previous 5 posts
					if (i > 5) break;

					item.Content += "<br /><br /><hr style=\"text-align:left;width:300px;\" /><strong>"
									+ previousPost.Subject + "</strong><br />" + GetAuthorInfo(previousPost, portalSettings) + "<hr style=\"text-align:left;width:300px;color:#bbbbbb;\" /><br />"
					                + HttpUtility.HtmlDecode(previousPost.Body);
				}
			}

			# endregion

			item.DateCreated = Utils.ConvertTimeZone(postInfo.CreatedDate, Config.GetForumConfig(postInfo.ModuleId));
			item.StartDate = postInfo.CreatedDate;
			item.ItemId = postInfo.PostID;
			item.Title = postInfo.Subject;
			item.Permalink = string.Empty;

			return item;
		}

		# endregion

		# region Add, Edit and Delete Methods

		/// <remarks>
		/// EditItem is called to edit either a page or a post.  The difference
		/// between the two is denoted in a property of the Item struct (ItemType).
		/// An additional property of the Item struct that may be important to the 
		/// implementation of the EditItem method is the Publish property.  This is 
		/// set to true when the item should be published.  The default setting of 
		/// false denotes that the item may be saved as a draft.
		/// </remarks>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="item"></param>
		/// <returns></returns>
		public string NewItem(string moduleLevelId, UserInfo userInfo, PortalSettings portalSettings, Item item)
		{
			// Authorization is handled in the shared UpdatePost procedure called below

			List<string> images = MetaPostServices.GetImageListFromItem(ref item);

			int forumId = Convert.ToInt32(moduleLevelId);
			// Tell the UpdatePost procedure that we're adding a new Forum.
			item.ItemId = "-1";
			int postId = UpdatePost(item, forumId, userInfo, portalSettings);

			MetaPostServices.SaveItemImages(postId, images, ProviderKey, portalSettings, userInfo.UserID);

			return postId.ToString();
		}

		/// <remarks>
		/// EditItem is called to edit either a page or a post.  The difference
		/// between the two is denoted in a property of the Item struct (ItemType).
		/// An additional property of the Item struct that may be important to the 
		/// implementation of the EditItem method is the Publish property.  This is 
		/// set to true when the item should be published.  The default setting of 
		/// false denotes that the item may be saved as a draft.
		///
		/// It's important to note that when Windows Live Writer sends the information 
		///		for a post edit, the moduleLevelId is not passed.  For implementation of 
		///		this procedure in providers which do not support pages, the moduleLevelId 
		///		should be ignored and attempts to get the moduleId can be made by calling
		///		GetModuleIdFromItemId or a similar private procedure.
		/// </remarks>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="item"></param>
		/// <returns></returns>
		public bool EditItem(string moduleLevelId, UserInfo userInfo, PortalSettings portalSettings, Item item)
		{
			// Authorization is handled in the shared UpdatePost procedure called below

			List<string> images = MetaPostServices.GetImageListFromItem(ref item);

			UpdatePost(item, userInfo, portalSettings);

			MetaPostServices.SaveItemImages(Convert.ToInt32(item.ItemId), images, ProviderKey, portalSettings, userInfo.UserID);

			return true;
		}

		/// <remarks>
		/// DeleteItem is used to delete either a page or a post.  The difference between the two is noted 
		/// in the itemType parameter which is an enumeration set to either Page or Post.
		/// </remarks>
		/// <param name="itemId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <param name="itemType"></param>
		/// <returns></returns>		
		public bool DeleteItem(string itemId, UserInfo userInfo, PortalSettings portalSettings, ItemType itemType)
		{
			PostController postController = new PostController();
			ForumController forumController = new ForumController();
			int postId = Convert.ToInt32(itemId);
			PostInfo postInfo = postController.PostGet(postId, portalSettings.PortalId);

			// Make sure user is authorized to call this method
			MetaPostServices.AuthorizeUser(AuthorizationType.ModuleLevelId, postInfo.ForumID.ToString(),
										   GetModulesForUser(userInfo, portalSettings, ProviderKey));

			ForumInfo forumInfo = forumController.GetForum(postInfo.ForumID);
			Config forumConfig = Config.GetForumConfig(forumInfo.ModuleID);
			ModuleSecurity security =
				new ModuleSecurity(forumInfo.ModuleID, GetTabId(forumInfo.ForumID.ToString()),
				                   forumInfo.ForumID, userInfo.UserID);
			bool styleDetectionPost = IsStyleDetectionPost(postInfo);

			if (security.IsForumModerator() || styleDetectionPost)
			{
				string notes =
					MetaPostServices.GetString("DeleteNotes", "Item deleted from WLW.", LocalizationFilePath, portalSettings.DefaultLanguage);

				string ProfileUrl = Utils.MySettingsLink(GetTabId(forumInfo.ForumID.ToString()),forumInfo.ModuleID);
				string url = Utils.ContainerViewForumLink(GetTabId(forumInfo.ForumID.ToString()), forumInfo.ForumID, false);

				// Send email if needed.
				if (forumConfig.MailNotification && !styleDetectionPost)
				   {
				   	// send notice to the user
				   	Utils.SendForumMail(postId, url, ForumEmailType.UserPostDeleted, notes, forumConfig,
				   	                    ProfileUrl, portalSettings.PortalId);
				   	// now send notice to the forum moderators
				   	Utils.SendForumMail(postId, url, ForumEmailType.ModeratorPostDeleted, notes, forumConfig,
				   	                    ProfileUrl, portalSettings.PortalId);
				   }
				
				// Get the author before we perform the delete.
				ForumUser author = postInfo.Author;
				

				postController.PostDelete(postId, postInfo.ModuleId, notes, portalSettings.PortalId,
				                          forumInfo.GroupID);

				if (!styleDetectionPost)
				{
					PostInfo.ResetPostInfo(postId);
					ThreadInfo.ResetThreadInfo(postInfo.ThreadID);
					ForumInfo.ResetForumInfo(forumInfo.ForumID);
					ForumUserController.ResetForumUser(author.UserID);

					ThreadController.ResetThreadListCached(forumInfo.ForumID, forumInfo.ModuleID);
					if (forumConfig.AggregatedForums)
					{
						ThreadController.ResetThreadListCached(-1, forumInfo.ModuleID);
					}
				}
			}
			else
			{
				throw new MetaPostException("NoDeleteRights","The rights to delete a thread are only available to forum moderators.");
			}

			return true;
		}

		private bool IsStyleDetectionPost(PostInfo postInfo)
		{
			bool styleDetectionPost;
			styleDetectionPost = (postInfo.Subject.Contains("3bfe001a-32de-4114-a6b4-4005b770f6d7")
								  && postInfo.Body.Contains("3bfe001a-32de-4114-a6b4-4005b770f6d7"));
			return styleDetectionPost;
		}

		# endregion

		# region Category Related Procedures

		/// <remarks>
		/// GetCategories retrieves a list of categories for a particular module.
		/// </remarks>
		/// <param name="moduleLevelId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns></returns>		
		public ItemCategoryInfo[] GetCategories(string moduleLevelId, UserInfo userInfo, PortalSettings portalSettings)
		{
			List<ItemCategoryInfo> catInfoList = new List<ItemCategoryInfo>();

			return catInfoList.ToArray();
		}

		public bool SetItemCategories(string itemId, ItemCategory[] itemCategories, UserInfo userInfo,
								PortalSettings portalSettings)
		{
			// Not yet implemented.
			return true;
		}

		public ItemCategory[] GetItemCategories(string itemId, UserInfo userInfo,
												PortalSettings portalSettings)
		{
			List<ItemCategory> itemCategories = new List<ItemCategory>();
			return itemCategories.ToArray();
		}

		/// <remarks>
		/// Used to add a new category for modules which support this feature.
		/// </remarks>
		/// <param name="moduleLevelId"></param>
		/// <param name="category"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns></returns>
		public int NewCategory(string moduleLevelId, NewItemCategory category, UserInfo userInfo, PortalSettings portalSettings)
		{
			int categoryId = 0;
			// Check first to see if the user should have the ability to add Categories

			// Implementation will go here.

			return categoryId;
		}

		# endregion

		#endregion

		# region Private Procedures Specific to Forum Module

		/// <remarks>
		/// UpdatePost is the procedure which handles updating the database with the
		/// changes made to the Forum entry.
		/// </remarks>
		/// <param name="item"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns>The PostId as an integer.</returns>
		private int UpdatePost(Item item, UserInfo userInfo, PortalSettings portalSettings)
		{
			int forumId;
			int postId = Convert.ToInt32(item.ItemId);

			PostController postController = new PostController();
			PostInfo post = postController.PostGet(postId, portalSettings.PortalId);
			forumId = post.ForumID;

			return UpdatePost(item, forumId, userInfo, portalSettings);
		}
		/// <remarks>
		/// UpdatePost is the procedure which handles updating the database with the
		/// changes made to the Forum entry.
		/// </remarks>
		/// <param name="item"></param>
		/// <param name="forumId"></param>
		/// <param name="userInfo"></param>
		/// <param name="portalSettings"></param>
		/// <returns>The PostId as an integer.</returns>
		private int UpdatePost(Item item, int forumId, UserInfo userInfo, PortalSettings portalSettings)
		{

			// Make sure user is authorized to call this method
			MetaPostServices.AuthorizeUser(AuthorizationType.ModuleLevelId, forumId.ToString(),
										   GetModulesForUser(userInfo, portalSettings, ProviderKey));

			PostInfo postInfo;
			PostController postController = new PostController();
			//ThreadController threadController = new ThreadController();
			//ThreadInfo threadInfo = threadController.ThreadGet(threadId);
			ForumController forumController = new ForumController();
			ForumInfo forumInfo = forumController.GetForum(forumId);
			Config forumConfig = Config.GetForumConfig(forumInfo.ModuleID);
			bool isUpdateOfExistingPost = false;
			ForumEmailType _emailType;
			ForumUserController forumUserController = new ForumUserController();
			ForumUser forumUser =
				forumUserController.UserGet(portalSettings.PortalId, userInfo.UserID, forumInfo.ModuleID);
			ModuleSecurity moduleSecurity = new ModuleSecurity(forumInfo.ModuleID, GetTabId(forumInfo.ForumID.ToString()), 
					forumInfo.ForumID, userInfo.UserID);

			int postId = Convert.ToInt32(item.ItemId);

			if (postId == -1)
			{
				// We need to create a new post
				postInfo = new PostInfo();
				postInfo.PostID = -1;
			}
			else
			{
				// Retrieve an existing post
				postInfo = postController.PostGet(postId, portalSettings.PortalId);
			}

			
			// Check first to see if the user should be editing this post.

			// Check to see if the thread is open and that if it's not open that the reply is 
			// being made by user who created the post and that either the forum is public or the 
			// user has the rights to post a restricted reply.  We also add code to allow forum
			// moderators to reply at any time.
			ThreadController threadController = new ThreadController();
			ThreadInfo threadInfo = threadController.ThreadGet(postInfo.ThreadID);

			if (threadInfo != null)
			{
				if (threadInfo.IsClosed && !moduleSecurity.IsForumModerator())
				{
					if (postInfo.ParentThread.ParentForum.PublicPosting)
					{
						if (postInfo.ParentThread.StartedByUser.UserID != userInfo.UserID)
						{
							throw new MetaPostException("NoReplyPrivilege",
							                            "The user for this account does not have the necessary privileges to reply to this message.");
						}
					}
					else
					{
						if (!moduleSecurity.IsAllowedToPostRestrictedReply() ||
						    (userInfo.UserID != postInfo.ParentThread.StartedByUser.UserID))
						{
							throw new MetaPostException("NoReplyPrivilege",
							                            "The user for this account does not have the necessary privileges to reply to this message.");
						}
					}
				}

				if (!threadInfo.IsClosed && threadInfo.ParentForum.IsActive
				    && !postInfo.ParentThread.ParentForum.PublicPosting
				    && !moduleSecurity.IsAllowedToPostRestrictedReply())
				{
					throw new MetaPostException("NoReplyPrivilege",
					                            "The user for this account does not have the necessary privileges to reply to this message.");
				}
			}

			if (!forumInfo.IsActive && !moduleSecurity.IsForumModerator())
			{
				// This thread is not active and the user is also not a moderator
				throw new MetaPostException("NoReplyPrivilege", "The user for this account does not have the necessary privileges to reply to this message.");
			}

			// We cannot reply if:
			//	post is private and not allowed to post restricted reply
			//  !((!Post.ParentThread.ParentForum.PublicPosting) && (Security.IsAllowedToPostRestrictedReply & (user.UserID == Post.ParentThread.StartedByUser.UserID)))

			// For Page entries find and place the extended entry.
			if (item.ItemType == ItemType.Page)
			{
				string extendedContent;
				RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline;
				extendedContent = Regex.Match(item.Content, "<!--itcMore-->(?<extContent>.*)", options).Groups["extContent"].Value;
				item.ExtendedContent = extendedContent;
				item.Content = Regex.Replace(item.Content, "<!--itcMore-->.*", string.Empty,
									  options);
			}

			postInfo.Subject = item.Title;
			postInfo.Body = HttpUtility.HtmlEncode(item.Content);

			if (postInfo.PostID != -1 && string.IsNullOrEmpty(item.ExtendedContent))
			{
				// This is an attempt to edit an existing post
				isUpdateOfExistingPost = true;

			}

			# region FILTER CONTENT

			PortalSecurity oSecurity = new PortalSecurity();
			string remoteAddress = HttpContext.Current.Request.UserHostAddress;
			remoteAddress = oSecurity.InputFilter(remoteAddress, PortalSecurity.FilterFlag.NoMarkup);

			// run body against security filter
			//postInfo.Body = oSecurity.InputFilter(postInfo.Body, PortalSecurity.FilterFlag.NoScripting);
			if (forumConfig.DisableHTMLPosting)
			{
				postInfo.Body = Utils.StripHTML(postInfo.Body);
			}

			// run subject against security filter
			//CP - Move to NoHTML post 4.4.1
			postInfo.Subject = oSecurity.InputFilter(postInfo.Subject, PortalSecurity.FilterFlag.NoScripting);
			if (forumConfig.DisableHTMLPosting)
			{
				postInfo.Subject = Utils.StripHTML(postInfo.Subject);
			}

			// Filter bad word
			WordFilterController ctlWordFilter = new WordFilterController();
			if (forumConfig.EnableBadWordFilter)
			{
				postInfo.Body = ctlWordFilter.FilterBadWord(postInfo.Body);
			}
			if (forumConfig.FilterSubject)
			{
				postInfo.Subject = ctlWordFilter.FilterBadWord(postInfo.Subject);
			}

			# endregion

			if (postInfo.PostID == -1)
			{
				
				if ((!forumInfo.PublicPosting && moduleSecurity.IsAllowedToStartRestrictedThread()) | (forumInfo.PublicPosting))
				{
					postId = PostAddAdapter(0, forumInfo, postInfo, postController, portalSettings, userInfo, remoteAddress);

					ThreadInfo.ResetThreadInfo(postId);
					postInfo.ThreadID = postId;
					_emailType = ForumEmailType.UserNewThread;
				}
				else
				{
					throw new MetaPostException("NoPostPrivilege", "The user for this account does not have the necessary privileges to post to this forum.");
				}
			}
			else
			{

				if (isUpdateOfExistingPost)
				{
					postController.PostUpdate(postInfo.ThreadID, postInfo.PostID, true, postInfo.Subject,
											  postInfo.Body, false, DateTime.Now, false, userInfo.UserID, string.Empty,
											  portalSettings.PortalId, -1, -1);
					postId = postInfo.PostID;
					_emailType = ForumEmailType.UserPostEdited;
				}
				else
				{
					
				# region Remove PostReplyInstructions
					
					string postReplyInstructions = MetaPostServices.GetString("PostReplyInstructions",
					                                                          "Enter reply above this line.",
					                                                          LocalizationFilePath, portalSettings.DefaultLanguage);
					
					RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Singleline;

					postInfo.Body = Regex.Replace(postInfo.Body, "&lt;br /&gt;" + postReplyInstructions, string.Empty, options);
					postInfo.Body = Regex.Replace(postInfo.Body, "&lt;br&gt;" + postReplyInstructions, string.Empty, options);
					
				# endregion

					postId =
						PostAddAdapter(postInfo.PostID, forumInfo, postInfo, postController, portalSettings, userInfo,
						               remoteAddress);
					_emailType = ForumEmailType.UserPostAdded;
					
				}
				if (!item.StyleDetectionPost)
				{
					ThreadInfo.ResetThreadInfo(postId);
					PostInfo.ResetPostInfo(postId);
				}
			}

			if (!item.StyleDetectionPost)
			{
				ForumInfo.ResetForumInfo(forumId);
				ForumUserController.ResetForumUser(userInfo.UserID);

				ThreadController.ResetThreadListCached(forumId, forumInfo.ModuleID);
				if (forumConfig.AggregatedForums)
				{
					ThreadController.ResetThreadListCached(-1, forumInfo.ModuleID);
				}
			}

				string url =
					Utils.ContainerViewPostLink(GetTabId(forumId.ToString()), forumId, postInfo.ThreadID, postId);

				MetaPostServices.SetTempInstallUrl(ProviderKey, url);

			# region Send Email

			if (!item.StyleDetectionPost)
			{
				// Obtain a new instance of postinfo 
				PostInfo _newPostInfo = PostInfo.GetPostInfo(postId, portalSettings.PortalId);

				// Send notification email & update forum post added info
				string _returnApprovedURL = string.Empty; //GetApprovedPostReturnURL(_newPostInfo);
				//Dim params As String()
				string _mailURL;
				string ProfileUrl =
					Utils.MySettingsLink(GetTabId(forumInfo.ForumID.ToString()), forumInfo.ModuleID);

				// If the post is to be moderated
				if ((forumInfo.IsModerated) && (!forumUser.IsTrusted | (!moduleSecurity.IsUnmoderated())))
				{
					// we set this here as it requires a seperate mail call
					_emailType = ForumEmailType.ModeratorPostToModerate;

					// This will now take moderator directly to post to moderate from email.
					_mailURL = Utils.ContainerPostToModerateLink(GetTabId(forumInfo.ForumID.ToString())
					                                             , _newPostInfo.ForumID, _newPostInfo.ModuleId);

					if (forumConfig.MailNotification)
					{
						Utils.SendForumMail(postId, _mailURL, _emailType, "Moderated Post", forumConfig, ProfileUrl,
						                    portalSettings.PortalId);
						// possibly email user that their post is in queue - removed functionality from previous versions
					}
				}
				else
				{
					// This is for a non moderated post (posted by trusted user, or the forum is not moderated)
					// We have to determine which tab we want to view becuase of group feature used on dnn.com (for now leaving as tab posted from because of possible perms issues)
					_mailURL = Utils.ContainerViewThreadLink(GetTabId(forumInfo.ForumID.ToString())
					                                         , _newPostInfo.ForumID, _newPostInfo.ThreadID);

					if (forumConfig.MailNotification)
					{
						if (_emailType == ForumEmailType.UserPostEdited)
						{
							if (forumConfig.EnableEditEmails)
							{
								Utils.SendForumMail(postId, _mailURL, _emailType, "Unmoderated Post", forumConfig,
								                    ProfileUrl, portalSettings.PortalId);
							}
						}
						else
						{
							Utils.SendForumMail(postId, _mailURL, _emailType, "Unmoderated Post", forumConfig, ProfileUrl,
							                    portalSettings.PortalId);
						}
					}
				}
			}

			# endregion

			return postId;
		}

		private int PostAddAdapter(int parentPostId, ForumInfo forumInfo, PostInfo postInfo, PostController postController, PortalSettings portalSettings, UserInfo userInfo, string remoteAddress)
		{
			int postId;
			
			// 08/10/2008 - The signature was changed for the PostAdd method.  The new signature takes 17
			// arguments while the old signature took 15 arguments.

			MethodInfo miPostAdd = postController.GetType().GetMethod("PostAdd");
			ParameterInfo[] piParameters = miPostAdd.GetParameters();
			object[] methodParams;
			if (piParameters.Length == 15)
			{
				methodParams = new object[15];
				methodParams.SetValue(parentPostId, 0);
				methodParams.SetValue(forumInfo.ForumID, 1);
				methodParams.SetValue(userInfo.UserID, 2);
				methodParams.SetValue(remoteAddress, 3);
				methodParams.SetValue(true, 4);
				methodParams.SetValue(postInfo.Subject, 5);
				methodParams.SetValue(postInfo.Body, 6);
				methodParams.SetValue(false, 7);
				methodParams.SetValue(DateTime.Now, 8);
				methodParams.SetValue(false, 9);
				methodParams.SetValue(0, 10);
				methodParams.SetValue(string.Empty, 11);
				methodParams.SetValue(portalSettings.PortalId, 12);
				methodParams.SetValue(-1, 13);
				methodParams.SetValue(-1, 14);
			}
			else
			{
				methodParams = new object[17];
				methodParams.SetValue(parentPostId, 0);
				methodParams.SetValue(forumInfo.ForumID, 1);
				methodParams.SetValue(userInfo.UserID, 2);
				methodParams.SetValue(remoteAddress, 3);
				methodParams.SetValue(true, 4);
				methodParams.SetValue(postInfo.Subject, 5);
				methodParams.SetValue(postInfo.Body, 6);
				methodParams.SetValue(false, 7);
				methodParams.SetValue(DateTime.Now, 8);
				methodParams.SetValue(false, 9);
				methodParams.SetValue(0, 10);
				methodParams.SetValue(string.Empty, 11);
				methodParams.SetValue(portalSettings.PortalId, 12);
				methodParams.SetValue(-1, 13);
				methodParams.SetValue(-1, 14);
				methodParams.SetValue(forumInfo.IsModerated, 15);
				methodParams.SetValue(forumInfo.GroupID, 16);
			}

			postId = (int)miPostAdd.Invoke(postController, methodParams);
			
			return postId;
		}

		private int GetTabId(string forumId)
		{
			// Check first to see if a TabId was passed in through the parameters
			int tabId = -1;

			if (HttpContext.Current.Request["sid"] != null)
			{
				try
				{
					tabId = Convert.ToInt32(HttpContext.Current.Request["sid"]);
				}
				catch
				{
					throw new MetaPostException("IntegerExpected", "Please enter an integer for the sid parameter.  The Forum metaPost provider expects a valid TabId.");
				}
				return tabId;
			}
			string SQL =
				"SELECT MAX(tm.TabID) AS TabID "
				+ "FROM {databaseOwner}{objectQualifier}Forum_Groups fg (nolock) "
				+ "		INNER JOIN {databaseOwner}{objectQualifier}Forum_Forums ff (nolock) ON fg.GroupID = ff.GroupID "
				+ "		INNER JOIN {databaseOwner}{objectQualifier}TabModules tm (nolock) ON fg.ModuleID = tm.ModuleID "
				+ "WHERE ff.ForumID = " + forumId.Replace("'", "''");
			using (IDataReader dr = DataProvider.Instance().ExecuteSQL(SQL))
			{
				while (dr.Read())
				{
					if (!dr.IsDBNull(0))
					{
						tabId = dr.GetInt32(0); // Only one column is returned (TabID)	
					}
						
				}
			}

			return tabId;
		}

		private string GetAuthorInfo(PostInfo postInfo, PortalSettings portalSettings)
		{
			RegexOptions options = RegexOptions.Singleline | RegexOptions.IgnoreCase;

			string authorInfo =
				MetaPostServices.GetString("PostAuthorInfo",
							   "by [DisplayName] on <span class=\"Forum_HeaderText\">[CreatedDate|d] at [CreatedTime|t]</span>",
							   LocalizationFilePath, portalSettings.DefaultLanguage);

			string createDateFormat =
				Regex.Match(authorInfo, @"\[createddate\|(?<pattern>[^\]])\]", options).Groups["pattern"].
					ToString();
			string createTimeFormat =
				Regex.Match(authorInfo, @"\[createdtime\|(?<pattern>[^\]])\]", options).Groups["pattern"].
					ToString();


			authorInfo =
				Regex.Replace(authorInfo, @"\[displayname\]", postInfo.Author.DisplayName,
							  options);


			if (createDateFormat != null)
			{
				authorInfo =
					Regex.Replace(authorInfo, @"\[createddate\|(?<pattern>[^\]])\]",
								  postInfo.CreatedDate.ToString(createDateFormat),
								  options);
			}
			if (createTimeFormat != null)
			{
				authorInfo =
					Regex.Replace(authorInfo, @"\[createdtime\|(?<pattern>[^\]])\]",
								  postInfo.CreatedDate.ToString(createTimeFormat),
								  options);
			}
			return authorInfo;
		}

		# endregion

		# region Private Comparer Classes

		private class ModuleInfoStructComparer : IComparer<ModuleInfoStruct>
		{
			public int Compare(ModuleInfoStruct x, ModuleInfoStruct y)
			{
				return x.ModuleName.CompareTo(y.ModuleName);
			}
		}

		private class ItemComparer : IComparer<Item>
		{
			public int Compare(Item x, Item y)
			{
				// Sort in descending order.
				return y.DateCreated.CompareTo(x.DateCreated);
			}
		}

		private class PostInfoComparer : IComparer<PostInfo>
		{
			public int Compare(PostInfo x, PostInfo y)
			{
				// Sort in descending order.
				return y.PostID.CompareTo(x.PostID);
			}
		}

		# endregion

	}
}